#include "simple/io/open.h"
#include "simple/io/write.h"
#include "simple/io/read.h"
#include "simple/io/read_iterator.h"
#include "simple/io/resize.h"
#include "simple/io/execute.h"
#include "simple/io/seek.h"

#include <array>

// aaaaaaaaaaaaaaaa
#include "simple/support/bits.hpp"
#include "simple/support/algorithm/traits.hpp"
#include "simple/support/algorithm/utils.hpp"
#include "simple/support/algorithm/mismatch.hpp"
#include "simple/support/type_traits/is_template_instance.hpp"
#include <charconv>

using namespace std::literals;

using namespace simple;
using namespace io;


template <typename B, std::enable_if_t<std::is_same_v<B,bool>>* = nullptr>
[[nodiscard]] std::string get_message(const B& b)
{
	return "bool: "s + (b ? "true" : "false");
}

namespace can_to_string_helper
{
	using std::to_string;
	template <typename T, typename Enabled = std::nullptr_t>
	struct check : std::false_type {};
	template <typename T>
	struct check<T, decltype(void(to_string(std::declval<T>())), nullptr)>
		: std::true_type {};
} // namespace can_to_string_helper
template <typename T>
struct can_to_string : can_to_string_helper::check<T> {};
template <typename T>
constexpr bool can_to_string_v = can_to_string<T>::value;

template <typename R, std::enable_if_t<support::is_template_instance_v<support::range,R>>* = nullptr>
[[nodiscard]] std::string get_message(const R& r)
{
	using value_type = typename R::bounds_type::value_type;
	// TODO if pointer and null don't size, cause that UBish
	std::string message = "simple::support::range<[typeid]"s + typeid(value_type).name() + "[typeid]>: "s
		+ (r.valid() ? "valid" : "invalid");
	if constexpr (support::is_range_v<R>)
	{
		// TODO: handle well known iterators
		// at least contiguous container iterators can be converted to pointers in C++20 to_address
		// naive linked list would have nullptr as end iterator, which could also be useful info
		// the node addresses as well could be more or less meaningful dependent on the allocator
		if constexpr (std::is_pointer_v<value_type>)
		{
			// convert to integer so that it's safe to work with
			support::range ir{
				reinterpret_cast<std::uintptr_t>(r.begin()),
				reinterpret_cast<std::uintptr_t>(r.end())
			};
			// now this is not UB, even if the pointers are bogus
			message += " | size " + std::to_string(ir.upper() - ir.lower());

			// and to print had to convert to integer anyway
			std::string ptr;
			auto min_bit_count = [](auto x){ return x == 0 ? 1 : support::bit_count(x) - support::count_leading_zeros(x); };
			auto ptrstr = [&](auto x)
			{
				auto ptr_bit_count = min_bit_count(x);
				ptr.resize(ptr_bit_count / 4 + ((ptr_bit_count % 4) != 0));
				std::to_chars(ptr.data(), ptr.data() + ptr.size(), x, 16);
			};
			ptrstr(ir.lower());
			message += " | bounds [" + ptr;
			ptrstr(ir.upper());
			message += ", " + ptr + ")";
		}
	}
	else // not an iterable range, assume arithmetic
	{
		message += " | size " + std::to_string(r.upper() - r.lower());
		using std::to_string;
		if constexpr (can_to_string_v<value_type>)
			message += " | bounds [" + to_string(r.lower()) + ", " + to_string(r.upper()) + ")";
	}
	message += " |";
	return message;
}

template <typename A, std::enable_if_t<std::is_arithmetic_v<A> && not std::is_same_v<A,bool>>* = nullptr>
[[nodiscard]] std::string get_message(const A& a)
{
	return "Builtin arithmetic ([typeid]"s + typeid(a).name() + "[typeid]): " + std::to_string(a);
}

template <typename StdStr, std::enable_if_t<std::is_same_v<StdStr, std::string>>* = nullptr>
[[nodiscard]] std::string get_message(const StdStr& str)
{
	// TODO: limit string size, huge strings are not useful as messages
	// and yet you do want a reasonable amount of structured text like say JSON
	return "std::string: size "s + std::to_string(str.size()) + " | " + str;
}

template <typename... Ts>
[[nodiscard]] std::string get_message(const meta::list<Ts...>&)
{
	return "meta::list: size "s + std::to_string(sizeof...(Ts)) + ((" | " + get_message(Ts{})) + ...);
}

template <typename T, typename Enabled = std::nullptr_t>
struct can_get_message : std::false_type {};
template <typename T>
struct can_get_message<T, decltype(void(get_message(std::declval<T>())), nullptr)>
	: std::true_type {};
template <typename T>
constexpr bool can_get_message_v = can_get_message<T>::value;

template <typename T, std::enable_if_t<not can_get_message_v<T>>* = nullptr>
[[nodiscard]] std::string get_message(const T& t)
{
	// TODO: if trivially copyable show byte representation in hex?
	// TODO: typeid is rtti and not super readable, might be better to just print  __PRETTY_FUNCTION__ (c++20 function_name)
	return std::string("[typeid]") + typeid(t).name() + "[typeid]";
};

template <typename Error, typename F = std::nullptr_t, typename... Args>
class exception_wrapper : public std::runtime_error, public Error
{
	public:
	std::tuple<Args...> args;
	F f = nullptr;

	exception_wrapper(Error error, F&& f, Args&&... args) :
		std::runtime_error(
			"Function: " + get_message(f) + '\n' +
			(("Argument: " + get_message(args) + '\n') + ... + ""s) +
			"Error: " + get_message(error)),
		Error(std::move(error)),
		args{std::forward<Args>(args)...},
		f(std::forward<F>(f))
	{}

	exception_wrapper(Error error) :
		std::runtime_error("Error: " + get_message(error)),
		Error(std::move(error))
	{}
};
template <typename Error, typename F, typename... Args>
exception_wrapper(Error error, F&& f, Args&&... args) -> exception_wrapper<Error, F, Args...>;

template <std::size_t index = 0, typename Variant = void,
	std::enable_if_t<support::is_template_instance_v<std::variant, Variant>>* = nullptr>
// TODO: [[nodiscard]] unless the required alternative is monostate
decltype(auto) require(Variant&& var)
{
	// static_assert(first variant alternative does not repeat);
	return std::visit([](auto&& a) -> decltype(std::get<index>(std::forward<Variant>(var)))
	{
		if constexpr (std::is_same_v<
			std::variant_alternative_t<index, Variant>,
			std::remove_reference_t<decltype(a)>>
		) return std::forward<decltype(a)>(a);
		else throw exception_wrapper(a);
	}, std::forward<Variant>(var));
};

template <std::size_t index = 0, typename F = void, typename... Args,
	typename Variant = std::invoke_result_t<F, Args...>,
	std::enable_if_t<support::is_template_instance_v<std::variant, std::remove_reference_t<Variant>>>* = nullptr
>
// TODO: [[nodiscard]] unless the required alternative is monostate
decltype(auto) require(F&& f, Args&&... args)
{
	// static_assert(first variant alternative does not repeat);
	return std::visit([fargs = std::forward_as_tuple(std::forward<F>(f), std::forward<Args>(args)...)](auto&& a)
		-> std::variant_alternative_t<index, Variant>
	{
		if constexpr (std::is_same_v<
			std::variant_alternative_t<index, Variant>,
			std::remove_reference_t<decltype(a)>>
		) return std::forward<decltype(a)>(a);
		else throw std::apply([a](auto&&... eargs)
			// TODO if earg is move only and passed as lvalue, only send off its type with the exception, leave the value untouched... currently cryptic compiler error instead
			{ return exception_wrapper(a, std::forward<decltype(eargs)>(eargs)...); },
		fargs);
	}, std::invoke(std::forward<F>(f), std::forward<Args>(args)...));
};

// TODO: request(F, Args...) noexcept - return first variant arg with index >0 or F(Args...)
// assert that only one arg is error (index>0), otherwise have to tuple and that can get out of hand quick, up to the user to reconcile independent operations

int main() try
{
	auto sf = require( open_f(meta::list(mode::write, mode::create), "helotherefile") );
	auto data = as_byte_view("uomo");

	// we offer up sf but it won't be taken unless error occurs in which case it'll be sent off with the exception
	// in general std move should really be called std::offer
	assert(( require( write_f, std::move(sf), data ) == data.end() ));
	assert(( require( write_f, std::move(sf), data ) == data.end() ));
	assert(( require( write_f, std::move(sf), data ) == data.end() ));

	std::array<std::byte, 10> buf{};
	std::byte* red = require( read_f, standard_input, as_byte_range(buf) );
	while(red)
	{
		assert(( require( write_f, standard_output, byte_view{buf.data(), red} ) == red ));
		red = require( read_f, standard_input, as_byte_range(buf));
	}

	const char* expected = "uomouomouomo";
	auto expected_data = as_byte_view(expected);
	auto rd = require( open_f, meta::list(mode::read), "helotherefile"s );
	assert(( support::mismatch(read_iterator(rd, as_byte_range(buf)), read_iterator(),
		expected_data.begin(), expected_data.end()) == std::pair{read_iterator(), expected_data.end()} ));

	assert( require( size_f, rd ) == 12 );
	require( resize_f, sf, 3 );
	assert( require( size_f, rd ) == 3 );
	require( seek_f, rd, 0 );

	auto resized = as_byte_view("uom");
	assert(( support::mismatch(read_iterator(rd, as_byte_range(buf)), read_iterator(),
		resized.begin(), resized.end()) == std::pair{read_iterator(), resized.end()} ));

	{
		auto expected_echo = as_byte_view("uomouomouomo\n");
		auto [process, out] = require( execute_f, meta::list{standard_output}, "echo"s, "echo"s, expected );
		assert(( support::mismatch(read_iterator(out, as_byte_range(buf)), read_iterator(),
			expected_echo.begin(), expected_echo.end()) == std::pair{read_iterator(), expected_echo.end()} ));
	}

	// TODO: remove them test files
	return 0;
}
catch(...)
{
	perror("omaaaagooot");
	throw;
}
